Skip to content

feat: implement SQLite Adapter#5

Draft
buibaoanh wants to merge 25 commits into1-no-orm-core-v1from
no-orm-sqlite-adapter
Draft

feat: implement SQLite Adapter#5
buibaoanh wants to merge 25 commits into1-no-orm-core-v1from
no-orm-sqlite-adapter

Conversation

@buibaoanh
Copy link
Copy Markdown

@buibaoanh buibaoanh commented Apr 17, 2026

Summary by CodeRabbit

  • New Features

    • Added SQLite adapter providing persistent data storage with create, read, update, delete, and upsert operations.
    • Added JSON field filtering with nested path support for querying complex data structures.
    • Added transaction support with automatic rollback on errors.
    • Added offset-based and cursor-based pagination for query results.
  • Documentation

    • Updated API initialization and migration examples in README.
    • Added limitations section documenting constraints and potential risks.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c4d9dd9-4a07-4152-baba-5aa068077575

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a complete SQLite adapter implementation with comprehensive test coverage, and updates the core Adapter interface to support JSON-path filtering through a new optional path property in Where and SortBy types.

Changes

Cohort / File(s) Summary
SQLite Adapter Implementation
src/adapters/sqlite.ts, src/adapters/sqlite.test.ts
Introduces new SqliteAdapter class implementing the Adapter interface with full CRUD operations, SQL generation for queries/mutations, JSON-field handling via json_extract, upsert with conflict resolution, transaction support using SQLite savepoints, pagination (offset and cursor-based), and comprehensive test suite covering single/multi-row operations, filtering, sorting, JSON paths, transactions, and rollback scenarios.
Core Interface Updates
src/core.ts
Tightens generic constraints across Adapter methods from T = Record<string, unknown> to T extends Record<string, unknown> = Record<string, unknown>, adds optional path?: string[] property to Where predicate variants and SortBy for JSON-path filtering support, and reformats delete/count argument types to multi-line format.
Documentation
README.md
Updates API examples to reflect simplified adapter initialization (new SqliteAdapter(schema, db) instead of object form), adds nested JSON query example with path-based filtering, and documents new "Limitations" section covering upsert constraints with JSON-path conflicts and concurrent transaction risks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • no-orm#4: The main implementation directly builds upon and extends the core Adapter/Where/SortBy types introduced in that PR, specifically by tightening generic constraints and adding the new path?: string[] property for JSON-path filtering.

Poem

🐰 A SQLite adapter hops into the fray,
With schemas and queries to save the day,
JSON paths extracted with json_extract might,
Transactions with savepoints—everything's tight!
Let the data flow free, no more dismay! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement SQLite Adapter' directly and accurately describes the main change: adding a complete SQLite adapter implementation with schema migrations, CRUD operations, transactions, and pagination support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch no-orm-sqlite-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
src/adapters/sqlite.test.ts (1)

408-432: Consider adding coverage for multi-field cursors and JSON-path sort/select.

Current pagination tests only exercise a single sort field (age). Given FieldName<T> advertises ->> paths and the cursor can carry multiple keys, tests that fail today would help surface the gaps in the adapter (multi-field keyset ordering and JSON-path handling in sortBy/select/cursor). Tying these to the bugs flagged in sqlite.ts would lock in the fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/sqlite.test.ts` around lines 408 - 432, Add tests that exercise
multi-field keyset pagination and JSON-path sort/select to cover gaps in the
adapter: extend the existing adapter.findMany<User> tests (the "should handle
cursor pagination ascending/descending" cases) to include sortBy with multiple
fields (e.g., [{ field: "age", direction: "asc" }, { field: "id", direction:
"asc" }]) and cursor.after carrying multiple keys (e.g., { age: X, id: Y }), and
add cases where sort/select use JSON-path style FieldName (->> paths) to ensure
sqlite.ts code handling sortBy, select and cursor extraction supports JSON-paths
and multi-key comparisons; update or add expectations for ordering and lengths
to validate correct multi-field keyset behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/adapters/sqlite.test.ts`:
- Around line 358-360: Update the misleading inline comment near the assertions
that check outer1?.name and outer2 to explain that the inner transaction's
UPDATE was discarded by the rollback to the savepoint (not "reverted"
afterwards); locate the assertion using outer1 and outer2 in the test and
replace "Reverted the update from inner tx" with wording like "Inner
transaction's UPDATE was discarded by rollback to savepoint (never applied to
outer scope)" so it clearly states the inner tx effects were not committed.

In `@src/adapters/sqlite.ts`:
- Around line 238-240: The update currently issues UPDATE ... WHERE ... then
calls this.find({ model, where }) which can return null if the update changed a
column used in the original where predicate; modify the update logic in the
update method to refetch by the record's primary key instead: either derive the
PK from data if it includes the primary key, or perform a pre-update read (using
the same model/where) to capture the primary key before running await
this.db.run(`UPDATE ${this.quote(model)} SET ${setClause} WHERE ${whereSql}`,
params), then call this.find({ model, where: { [primaryKeyName]: primaryKeyValue
} }) to reliably return the updated record (refer to symbols update, this.find,
quote(model), setClause, whereSql, params).
- Around line 142-152: The create method currently returns the caller-supplied
data when select is omitted, which bypasses DB-side defaults/coercions; update
create to always refetch the persisted row from the DB (use this.find) after
insertion using the new record's primary key (use model PK info to build the
where clause) and return that fetched result instead of the original input;
ensure the code path that currently does "if (result === null) throw ..."
remains but trigger the find by PK regardless of whether select was provided so
returned object reflects actual persisted state.
- Around line 445-456: The current multi-field cursor predicate builds a simple
AND chain using cursorDirection derived from sortBy[0], which is incorrect for
lexicographic keyset pagination; update the logic in the cursor handling block
(where cursor, cursorDirection, this.quote, mapWhereValue, parts, and params are
used) to generate a lexicographic OR-chain: for n cursor fields produce clauses
like (f1 > ? ) OR (f1 = ? AND f2 > ? ) OR ... (or use a row-value comparison if
preferred), pushing appropriate mapped params for each clause, and ensure you
respect each field's individual sort direction from sortBy (not just sortBy[0])
when choosing '>' vs '<' for that field.
- Around line 499-509: The JSON path construction in the block handling
field.includes("->>") builds jsonPath via `'$.${parts.slice(1).join(".")}'`,
which can be injected if a segment contains a quote or invalid characters; in
the function where quotedField is built (look for variables: field, parts,
topLevelColumn, jsonPath, this.quote(topLevelColumn), and json_extract),
validate each segment parts.slice(1) against /^[A-Za-z_][A-Za-z0-9_]*$/ and
throw an Error for invalid segments, and/or escape single quotes by replacing
any "'" with "''" before joining so the final string passed into json_extract is
safe. Ensure the topLevelColumn continues to use this.quote(topLevelColumn) and
return the sanitized quotedField.
- Around line 384-396: The transaction method uses SAVEPOINTs on a shared
connection which allows concurrent top-level adapter.transaction(...) calls to
interleave and corrupt each other; fix by serializing top-level transactions
(e.g., add an internal mutex/queue like this.txMutex and acquire/release it
around the SAVEPOINT/RELEASE/ROLLBACK block in transaction so only one top-level
transaction runs at a time while nested calls still use savepoints), replace the
ad-hoc sp generation with a monotonic counter (e.g., this._spCounter++) to
guarantee uniqueness, and quote the savepoint identifier when executing
`SAVEPOINT`, `RELEASE SAVEPOINT`, and `ROLLBACK TO SAVEPOINT` (use a safe quoted
form like double-quotes) so identifiers are safe against refactors.
- Around line 325-382: The upsert implementation in upsert(...) ignores the
passed where clause and always keys ON CONFLICT/refetch on
modelSpec.primaryKey.fields and uses create[f] for PK refetch, which is unsafe;
change behavior to enforce that args.where is a simple set of equality
conditions covering every PK field (throw if not) and then use those equality
values (not create or update) when building the conflict/refetch key and
pkValuesWhere for the subsequent find call; also disallow or ignore PK fields in
update (or throw) so an update cannot change the PK used for refetch; update
references: upsert, args.where, modelSpec.primaryKey.fields, ON CONFLICT(...),
pkValuesWhere, mappedCreate, mappedUpdate, and find.
- Around line 195-200: The ORDER BY, buildSelect, and cursor construction use
this.quote(...) which incorrectly wraps JSON-path field names like
"metadata->>theme"; add a helper method (e.g., quoteField(field: FieldName<T>))
that detects JSON path syntax (-> and ->>) and rewrites it into
json_extract(...) expressions with proper quoted identifiers and unwrapping for
->> while leaving plain identifiers quoted via this.quote, then replace calls to
this.quote(s.field)/this.quote(f) in the sortBy mapper (ORDER BY builder),
buildSelect, and the cursor loop with this.quoteField(...) so JSON paths are
handled consistently (also keep buildWhereRecursive unchanged which already
handles JSON paths).

---

Nitpick comments:
In `@src/adapters/sqlite.test.ts`:
- Around line 408-432: Add tests that exercise multi-field keyset pagination and
JSON-path sort/select to cover gaps in the adapter: extend the existing
adapter.findMany<User> tests (the "should handle cursor pagination
ascending/descending" cases) to include sortBy with multiple fields (e.g., [{
field: "age", direction: "asc" }, { field: "id", direction: "asc" }]) and
cursor.after carrying multiple keys (e.g., { age: X, id: Y }), and add cases
where sort/select use JSON-path style FieldName (->> paths) to ensure sqlite.ts
code handling sortBy, select and cursor extraction supports JSON-paths and
multi-key comparisons; update or add expectations for ordering and lengths to
validate correct multi-field keyset behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0fe2be9-040b-4d5a-b06f-85fdb56a57b8

📥 Commits

Reviewing files that changed from the base of the PR and between 72bf01d and 7c63bc8.

📒 Files selected for processing (4)
  • README.md
  • src/adapters/sqlite.test.ts
  • src/adapters/sqlite.ts
  • src/core.ts

Comment thread src/adapters/sqlite.test.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
Comment on lines +384 to +396
async transaction<T>(fn: (tx: Adapter) => Promise<T>): Promise<T> {
const sp = `sp_${Date.now()}_${Math.floor(Math.random() * 100000)}`;

await this.db.run(`SAVEPOINT ${sp}`, []);
try {
const result = await fn(this);
await this.db.run(`RELEASE SAVEPOINT ${sp}`, []);
return result;
} catch (error) {
await this.db.run(`ROLLBACK TO SAVEPOINT ${sp}`, []);
throw error;
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Transaction uses savepoints on the shared connection; concurrent top-level transaction() calls will interleave.

fn receives this (the adapter), so nested calls correctly use stacked SAVEPOINTs. However, two top-level await adapter.transaction(...) calls started in parallel will both open savepoints on the same underlying SqliteDatabase and interleave their statements, and an early ROLLBACK TO SAVEPOINT from one will undo the other's work within its range. Two minor related nits:

  • sp_${Date.now()}_${Math.floor(Math.random() * 100000)} is not guaranteed unique; prefer a monotonic counter.
  • SQLite identifiers for savepoints should be quoted to be safe against future refactors.

Consider documenting the single-in-flight-transaction assumption, or serializing transactions through an internal mutex.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/sqlite.ts` around lines 384 - 396, The transaction method uses
SAVEPOINTs on a shared connection which allows concurrent top-level
adapter.transaction(...) calls to interleave and corrupt each other; fix by
serializing top-level transactions (e.g., add an internal mutex/queue like
this.txMutex and acquire/release it around the SAVEPOINT/RELEASE/ROLLBACK block
in transaction so only one top-level transaction runs at a time while nested
calls still use savepoints), replace the ad-hoc sp generation with a monotonic
counter (e.g., this._spCounter++) to guarantee uniqueness, and quote the
savepoint identifier when executing `SAVEPOINT`, `RELEASE SAVEPOINT`, and
`ROLLBACK TO SAVEPOINT` (use a safe quoted form like double-quotes) so
identifiers are safe against refactors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodeRabbit is this still valid?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

Let me check the current state of the code on the PR branch.


🏁 Script executed:

#!/bin/bash
# Check the current state of the transaction method in sqlite.ts
rg -n "transaction|savepoint|SAVEPOINT|sp_|_spCounter|txMutex|mutex" src/adapters/sqlite.ts -i -A 2 -B 2

Length of output: 1236


Let me get more context on both the transaction implementations and README documentation.


🏁 Script executed:

#!/bin/bash
# Check the full transaction implementations
rg -n "transaction|BEGIN|COMMIT|ROLLBACK|inTransaction" src/adapters/sqlite.ts -A 3 -B 1

echo "---README---"
grep -n -i "limitation\|transaction\|concurrent" README.md -A 3 -B 1

Length of output: 2096


🏁 Script executed:

#!/bin/bash
# Get the full Limitations / Notes section from README
sed -n '/^## Notes/,/^## /p' README.md
echo "---"
# Also check if there's a Limitations section
sed -n '/^## Limitations/,/^## /p' README.md

Length of output: 501


🏁 Script executed:

#!/bin/bash
# Get the full Transactions section and surrounding context
sed -n '/^## Transactions/,/^## /p' README.md

Length of output: 402


@heiwenPartially. The code has been substantially refactored since the original comment, so two of the three concerns are resolved, but one remains:

Original concern Status
Non-unique sp_${Date.now()} savepoint names Resolved — SAVEPOINTs are no longer used
Unquoted savepoint identifiers Resolved — SAVEPOINTs are no longer used
Concurrent top-level transaction() calls interleaving on the shared connection ⚠️ Partially valid — see below

Remaining concern (async driver)

The refactored code now correctly uses BEGIN/COMMIT/ROLLBACK and the inTransaction flag prevents double-BEGIN on nested calls. For the sync driver (e.g. Bun's bun:sqlite), interleaving is impossible because JS is single-threaded and all driver calls are synchronous.

However, for the async SqliteDatabase driver, two concurrent top-level calls can still interleave:

// These run concurrently — both issue BEGIN, then queries interleave on the same connection
await Promise.all([
  adapter.transaction(async (tx) => { await tx.create(...); await tx.update(...); }),
  adapter.transaction(async (tx) => { await tx.create(...); await tx.update(...); }),
]);

Minor README inaccuracy

The README currently states "SQLite and Postgres support nested transactions via savepoints", but the current SQLite implementation does not use savepoints for nesting — nested calls simply reuse the outer adapter via fn(this). This line is inaccurate and could be misleading.

Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/adapters/sqlite.ts Outdated
@buibaoanh
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/adapters/sqlite.test.ts (1)

372-432: Pagination beforeEach is declared after the first test, masking shared seed data.

The beforeEach at Line 420 is registered for the whole Pagination describe (including the multi-field keyset pagination test above it), so that test actually runs against 10 rows (p1-p5 + m1-m5). It currently passes because the cursor {age: 30, name: "B"} happens to exclude all p* rows, but the test reads as if only m1-m5 exist and is fragile to future cursor changes.

Suggestion: move the beforeEach hook to the top of the Pagination describe, and either isolate the multi-field test into its own describe without the shared seed or extend the inline commentary to document that p1-p5 are also in-scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/sqlite.test.ts` around lines 372 - 432, The test "should handle
multi-field keyset pagination correctly" runs before the file-scoped beforeEach
that seeds p1-p5, causing it to see extra rows; either move the existing
beforeEach (the Promise.all seeding loop) to the top of the Pagination describe
so it runs for all tests consistently, or wrap this multi-field test in its own
describe block (or add a dedicated beforeEach inside that describe) to isolate
seeding for m1-m5; update/remove comments to document which seed set applies
accordingly and ensure references to adapter.create remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/adapters/sqlite.ts`:
- Around line 602-623: Handle empty arrays in the "in" and "not_in" branches:
detect when list.length === 0 after building list (in the case "in" and case
"not_in" blocks that call this.mapWhereValue and use quotedField), and
short-circuit to a safe always-false or always-true predicate instead of
emitting IN ()/NOT IN (); specifically return an SQL fragment like "1=0" (no
params) for empty "in" and "1=1" (no params) for empty "not_in" so
mapWhereValue, params array, and quotedField usage remain correct and no invalid
SQL is produced.

---

Nitpick comments:
In `@src/adapters/sqlite.test.ts`:
- Around line 372-432: The test "should handle multi-field keyset pagination
correctly" runs before the file-scoped beforeEach that seeds p1-p5, causing it
to see extra rows; either move the existing beforeEach (the Promise.all seeding
loop) to the top of the Pagination describe so it runs for all tests
consistently, or wrap this multi-field test in its own describe block (or add a
dedicated beforeEach inside that describe) to isolate seeding for m1-m5;
update/remove comments to document which seed set applies accordingly and ensure
references to adapter.create remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b46ff4b-a7a0-4534-9976-668446b44f28

📥 Commits

Reviewing files that changed from the base of the PR and between 7c63bc8 and b323bac.

📒 Files selected for processing (4)
  • README.md
  • src/adapters/sqlite.test.ts
  • src/adapters/sqlite.ts
  • src/core.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Comment thread src/adapters/sqlite.ts Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread src/core.ts Outdated
Comment thread src/core.ts Outdated
Comment thread src/core.ts Outdated
Comment thread src/adapters/sqlite.ts
Comment thread src/adapters/sqlite.ts Outdated
Comment thread src/core.ts Outdated
@heiwen heiwen marked this pull request as draft April 22, 2026 04:11
buibaoanh and others added 9 commits April 22, 2026 14:36
- Replace unsafe as FieldName<T> with 'in' checks and keyof T indexed accesses in getIdentityValues
- Add // eslint-disable-next-line comments for remaining necessary assertions in buildIdentityFilter
- Fix overly broad cast in postgres.ts from 'as Record<string, unknown>[]' to 'as Array<Record<string, unknown>>'

Co-authored-by: opencode <opencode@anomaly.co>
- Replace unsafe as FieldName<T> with 'in' checks and keyof T indexed accesses in getIdentityValues
- Add // eslint-disable-next-line comments for remaining necessary assertions in buildIdentityFilter
- Fix overly broad cast in postgres.ts from 'as Record<string, unknown>[]' to 'as Array<Record<string, unknown>>'

Co-authored-by: opencode <opencode@anomaly.co>
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
model: "conversations",
```ts
// Create
const created = await adapter.create<"users", User>({
Copy link
Copy Markdown
Contributor

@heiwen heiwen Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both "users" and User on the generic feels redundant. Can this be reduced to just User? Especially since the adapter is already typed by schema? (applies to all below)

Comment thread README.md
import { SqliteAdapter } from "@8monkey/no-orm/adapters/sqlite";

const db = new Database(":memory:");
const adapter = new SqliteAdapter(schema, db);
Copy link
Copy Markdown
Contributor

@heiwen heiwen Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The adapter should be typed by schema, shouldn't it? Same for the ones below.

Comment thread README.md Outdated
Comment thread tsconfig.json Outdated
Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
Comment thread src/adapters/sql.ts Outdated
Copy link
Copy Markdown
Contributor

@heiwen heiwen Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What in here is really SQL specific vs could also be used by a non-SQL adapter like MongoDB (and therefore should be in common.ts?)

Move common.ts and sql.ts into adapters/utils to make clear they are not actually adapters.

Comment thread src/adapters/memory.ts Outdated
Comment thread README.md Outdated
import { Database } from "bun:sqlite";
import { SqliteAdapter } from "@8monkey/no-orm/adapters/sqlite";

const db = new Database(":memory:");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or filename

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both is value-able, keep both.

Comment thread src/adapters/memory.ts Outdated
Comment thread src/adapters/memory.ts Outdated
Comment thread src/adapters/memory.ts Outdated
Comment thread src/adapters/memory.ts Outdated
Comment thread src/adapters/memory.ts Outdated
Comment thread src/adapters/postgres.ts Outdated
Comment thread src/adapters/postgres.ts

// --- Internal PG Syntax Helpers ---

const quote = (s: string) => `"${s.replaceAll('"', '""')}"`;
Copy link
Copy Markdown
Contributor

@heiwen heiwen Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a regex which is called dozens of times per query. How can we avoid that? Can we use safe interfaces from the drivers that do that for us?

Copy link
Copy Markdown
Author

@buibaoanh buibaoanh May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use driver-native identifier quoting?

While sql("identifier") in postgres.js and Bun.sql, pg.escapeIdentifier in node-postgres are valid for Postgres drivers, the three SQLite drivers (bun:sqlite, better-sqlite3, sqlite) have no quoting API.

Suggestion

For Postgres drivers that expose a native quoting — sql("identifier") in postgres.js and Bun.sql, pg.escapeIdentifier in node-postgres — we delegate quoting to the driver directly.

For SQLite drivers (bun:sqlite, better-sqlite3, sqlite), we use quote() instead. On top of this, we drop createQuotedSchema, which pre-caches quoted identifiers at construction time. That means eliminating the startup allocation and the cache lookup indirection, and simplifies the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants